package com.csaba.connector.otp;

import java.io.IOException;
import java.io.StringReader;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.htmlparser.jericho.Source;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;

import com.csaba.connector.I18NServiceException;
import com.csaba.connector.ServiceException;
import com.csaba.connector.model.Account;
import com.csaba.connector.model.Customer;
import com.csaba.connector.model.Session;
import com.csaba.connector.otp.model.OTPBank;
import com.csaba.connector.otp.model.OTPCustomer;
import com.csaba.connector.otp.model.OTPRequestFactory;
import com.csaba.connector.otp.model.OTPSession;
import com.csaba.connector.otp.parser.AccountBalanceParser;
import com.csaba.connector.otp.parser.LoginParser;
import com.csaba.connector.otp.parser.OTPCodes;
import com.csaba.connector.service.AbstractLoginService;
import com.csaba.connector.service.AccountHistoryService;
import com.csaba.connector.service.AccountService;
import com.csaba.connector.service.LogoutService;
import com.csaba.util.LoggerHelper;

//XXX implement dummy in OTP
//XXX implement ChangePasswordService in OTP
public class OTPLoginService extends AbstractLoginService
{
	private final static Logger logger = LoggerHelper.getDefaultLogger();

	public static final String ACCOUNT_PREFIX = "117"; //$NON-NLS-1$

	public final static int LOGINID_LENGTH = 7;
	public final static int LOGINID_MIN_LENGTH = 5;
	public final static int PASSWORD_LENGTH = 8;
	public final static int ACC1_LENGTH = 5;
	public final static int ACC2_LENGTH = 8;
	public final static int ACC3_LENGTH = 8;

	public final static String RP_ACCOUNT1 = "account1";
	public final static String RP_ACCOUNT2 = "account2";
	public final static String RP_ACCOUNT3 = "account3";

	private boolean smsAuthentication = false;

	@Override
	public void execute( final Session nullSession ) throws ServiceException
	{
		if ( customer == null )
			throw new ServiceException("Login service is not correctly initialized.");
		if ( nullSession != null )
			throw new ServiceException("Session is already initialized. Cannot login again in the same session.");

		customer = new OTPCustomer(customer);

		final String userId = customer.getLoginId();
		final String accountNr1 = (String) customer.getRemoteProperty(RP_ACCOUNT1);
		final String accountNr2 = (String) customer.getRemoteProperty(RP_ACCOUNT2);
		final String accountNr3 = (String) customer.getRemoteProperty(RP_ACCOUNT3);

		final NameValuePair[] loginData = new NameValuePair[] {// 
				new BasicNameValuePair("azonosito", userId),
				new BasicNameValuePair("jelszo", customer.getPassword()), // 
				new BasicNameValuePair("muvelet", "login"),
				new BasicNameValuePair("cacheHasznalat", "on"),
				new BasicNameValuePair("honlapAzonosito", "Default.User"),
				new BasicNameValuePair("tranzakcionkentiAzonositas", "off"),
				new BasicNameValuePair("x", String.valueOf((int) ( Math.random() * 30 ))), //mouse cursor coordinates on the "login" button
				new BasicNameValuePair("y", String.valueOf((int) ( Math.random() * 30 ))), //fill with random values.
				new BasicNameValuePair("szamlaSzam.kulso[0]", accountNr1),
				new BasicNameValuePair("szamlaSzam.kulso[1]", accountNr2),// 
				new BasicNameValuePair("szamlaSzam.kulso[2]", accountNr3), };

		this.session = new OTPSession();
		final OTPSession otp = (OTPSession) this.session;
		final String result = otp.doPost(OTPRequestFactory.HOMEBANK_REQUEST_URL_BASE + OTPRequestFactory.REQUEST_LOGIN,
				loginData);

		otp.debugFile("login.html", result);

		//decide on error 
		Source source;
		try
		{
			source = new Source(new StringReader(result));
		}
		catch ( final IOException e )
		{
			throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError1", e);
		}

		logger.fine("Detecting error...");
		if ( LoginParser.isErrorOccured(source) )
		{
			logger.fine("Error during login... Parsing messaging.");
			String message = null;
			try
			{
				message = LoginParser.parseLoginError(source);
			}
			catch ( final Exception ex )
			{
				throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError1", ex);
			}

			throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError2", message);
		}

		logger.fine("Detecting SMS login...");
		if ( LoginParser.isSMSLogin(result) )
		{
			( (OTPCustomer) customer ).setSmsAuthentication(smsAuthentication = true);
			otp.setCustomer(customer);

			//get profile for ajax test, in order to extract JSESSIONID
			final String url = LoginParser.extractProfileURL(result);
			if ( url == null )
				throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError2",
						"Nem találom a megfelelő hivatkozást.");

			final String ajaxTest = otp.doGet(OTPRequestFactory.HOMEBANK_SERVER_URL + url);
			otp.debugFile("ajaxTest", ajaxTest);
		}
		else
		{
			//normal password login
			( (OTPCustomer) customer ).setSmsAuthentication(smsAuthentication = false);
			otp.setCustomer(customer);

			postProcessLogin(otp, result, source);
		}
	}

	public static void postProcessLogin( final OTPSession otp, final String result, final Source source )
			throws ServiceException
	{
		logger.fine("Login was succesful.");

		/* EXTRACTING JS
		Element element = source.getNextElement(0, "script");
		while ( element != null )
		{
			final Attribute srcAttr = element.getAttributes().get("src");
			if ( srcAttr != null )
			{
				final String src = srcAttr.getValue();
				System.out.println("JS:" + src);
				getJS(otp, src);
			}
			element = source.getNextElement(element.getEnd(), "script");
		}

		/* */

		final Customer customer = otp.getCustomer();

		//fix customer properties
		customer.setId(customer.getLoginId());
		customer.setBank(OTPBank.getInstance());
		try
		{
			customer.setName(LoginParser.parseCustomerName(source));
		}
		catch ( final IOException e1 )
		{
			throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError1", e1);
		}
		customer.addPermission(AccountHistoryService.PERMISSION);
		customer.addPermission(AccountService.PERMISSION);

		//fix session properties
		otp.setCustomer(customer);

		//extract contract details
		//	   	var contract = {
		//	   			startingDate: "20030610120000",
		//	   			dia: "920636627334605726455",
		//	   			gwbid: "92063662"
		//	   	   	};

		final int contractStart = result.indexOf("var contract");
		boolean parsed = false;
		if ( contractStart > 0 )
		{
			final int braceOpen = result.indexOf('{', contractStart);
			final int braceClose = result.indexOf('}', contractStart);
			if ( braceOpen > 0 && braceClose > 0 && braceOpen < braceClose )
			{
				try
				{
					final JSONObject object = new JSONObject(result.substring(braceOpen, braceClose + 1));
					otp.setRemoteProperty(OTPCodes.RP_STARTING_DATE, object.getString("startingDate"));
					otp.setRemoteProperty(OTPCodes.RP_DIA, object.getString("dia"));
					otp.setRemoteProperty(OTPCodes.RP_GWBID, object.getString("gwbid"));
					parsed = true;
					if ( logger.isLoggable(Level.FINE) )
						logger.fine("OTP Contract: " + object);
				}
				catch ( final JSONException e )
				{
					throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError1", e);
				}
			}
		}

		if ( !parsed )
			throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError1",
					"Contract details is missing from login result.");

		//CHECK POLLER
		final String pollUrl = WorkflowPolling.parsePollTransaction(source);
		if ( pollUrl != null )
		{
			if ( ( new WorkflowPolling(otp, pollUrl) ).poll() )
			{
				final NameValuePair[] data = new NameValuePair[] {
						new BasicNameValuePair("kliensTranzakcioId", String.valueOf(otp.getOperationCounter())),
						new BasicNameValuePair("muveletStatusz", "varakozas"), };

				final String accountListResult = otp.doPost(OTPRequestFactory.HOMEBANK_REQUEST_URL_BASE
						+ OTPRequestFactory.REQUEST_GET_ACCOUNT_LIST, data);
				otp.debugFile("accountListReply.html", accountListResult);

				try
				{
					final Account[] accountList = AccountBalanceParser.extractAccountList(new StringReader(
							accountListResult), customer);
					otp.setRemoteProperty(OTPCodes.RP_ACCOUNT_LIST, accountList);
				}
				catch ( final IOException e )
				{
					throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError1", e);
				}
			}
			else
			{
				throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError2",
						"Számlalista nem jött le a várakozás után.");
			}
		}
		else
		{
			throw new I18NServiceException(OTPBank.getInstance(), "msg.loginError2",
					"Nem megfelelő login válasz. Várakozó instrukciók hiányoznak.");
		}

	}

	public boolean isSmsAuthentication()
	{
		return smsAuthentication;
	}

	public static void getJS( final OTPSession otp, final String js ) throws ServiceException
	{
		final String get = otp.doGet(OTPRequestFactory.HOMEBANK_SERVER_URL + js);
		final String fileName = js.substring(js.lastIndexOf("/"));
		otp.debugFile(fileName, get);
	}

	public static void main( final String[] args ) throws ServiceException, IOException
	{
		LoggerHelper.initLogger(Level.FINE);
		//Logger.getLogger("org.apache.commons.httpclient").setLevel(Level.FINE);

		final OTPLoginService login = new OTPLoginService();
		final Customer customer = new Customer();
		customer.setLoginId(args[0]);
		customer.setPassword(args[1]);
		customer.setRemoteProperty(RP_ACCOUNT1, args[2]);
		customer.setRemoteProperty(RP_ACCOUNT2, args[3]);
		customer.setRemoteProperty(RP_ACCOUNT3, "");
		login.setCustomer(customer);
		try
		{
			login.execute(null);
		}
		catch ( final ServiceException e )
		{
			logger.log(Level.SEVERE, "Native error:" + e.getNativeMessage(), e);
			throw e;
		}
		final Session session = login.getSession();
		System.out.println("Session ready: " + session);
		System.out.println("Customer: " + session.getCustomer());

		/* GET TOKEN
		new OTPGetTokenService().execute(session);
		 

		final String token = (String) session.getRemoteProperty(OTPCodes.RP_LASTTOKEN);
		System.out.println("TOKEN : " + token);
		*/

		/** FIXME test SMS auth
		if ( login.isSmsAuthentication() )
		{
			//get OTP
			final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
			String otp = null;
			while ( !( otp = reader.readLine() ).startsWith("otp") )
			{
				if ( otp.equals("stop") )
					return;
			}
			otp = otp.substring(3);
			System.out.println("Verifying otp: [" + session + "]");

			final OTPSMSLoginService smslogin = new OTPSMSLoginService();
			smslogin.setSmsCode(otp);

			try
			{
				smslogin.execute(session);
			}
			catch ( final ServiceException e )
			{
				logger.log(Level.SEVERE, "Native error:" + e.getNativeMessage(), e);
				throw e;
			}
		}

		///homebank/do/profile: what is it for? - this is for testing AJAX enabled browser only. Nothing useful is returned.
		*/

		try
		{
			final AccountService account = new OTPAccountService();
			account.execute(session);
			final Account[] accounts = account.getAccounts();
			if ( accounts != null )
			{
				for ( final Account acc : accounts )
				{
					logger.info("Account received: " + acc);
				}
			}
			else
			{
				logger.warning("No account is available.");
			}

			/* FIXME implement history service
			final AccountHistoryService history = new OTPAccountHistoryService();
			history.setAccount(accounts[0]);

			final Calendar to = Calendar.getInstance();
			final Calendar from = Calendar.getInstance();
			//from.roll(Calendar.DATE, false); //get last day transactions
			from.roll(Calendar.MONTH, false); //get last month transactions
			history.setDateRange(from.getTime(), to.getTime());

			history.execute(session);

			final HistoryItem[] items = history.getHistory();

			if ( items != null && items.length > 0 )
			{
				for ( int i = 0; i < items.length; i++ )
				{
					logger.fine("items[" + i + "]=" + items[i]);
				}
			}
			else
			{
				logger.fine("No transactions found for account: " + accounts[0]);
			}
			*/
		}
		catch ( final Exception e )
		{
			e.printStackTrace();
		}

		final LogoutService logout = new OTPLogoutService();
		logout.execute(session);

	}
}
